unit System.IO;

interface

uses
  System.SysUtils, Winapi.ShlObj, Winapi.Windows;

type
  TFile = record
    class procedure WriteAllText(path: string; contents: string); overload; static;
    class procedure WriteAllText(path: string; contents: string; encoding: TEncoding); overload; static;
  end;

  TSearchOption = (soTopDirectoryOnly, soAllDirectories);

  TDirectory = record
    class function GetFiles(path: string; searchPattern: string = '*'; searchOption: TSearchOption = soTopDirectoryOnly): TArray<string>; static;
    class function GetFolderPath(nFolder: Integer): string; static;
    class function GetDesktop: string; static; inline;
    class function GetAppData: string; static; inline;
    class function GetTempPath: string; static; inline;
  end;

  TPath = class
    const
      DirectorySeparatorChar = PathDelim;
  private
    class procedure CheckInvalidPathChars(path: string; checkAdditional: Boolean);
    class function HasIllegalCharacters(path: string; checkAdditional: Boolean): Boolean; static;
  end;

implementation

uses
  Classes, RegularExpressions;

{ TDirectory }

class function TDirectory.GetAppData: string;
begin
  Result := TDirectory.GetFolderPath(CSIDL_APPDATA);
end;

class function TDirectory.GetDesktop: string;
begin
  Result := TDirectory.GetFolderPath(CSIDL_DESKTOP);
end;

class function TDirectory.GetFiles(path, searchPattern: string; searchOption: TSearchOption): TArray<string>;

  function SearchFile(p: string): TArray<string>;
  var
    F: TSearchRec;
    found: Integer;
    ts: string;
    m: TMatch;
  begin
    Result := [];
    if not DirectoryExists(p) then
      Exit;
    found := FindFirst(p + PathDelim + '*.*', faAnyFile, F);
    while found = 0 do
    begin
      if not ((F.Name = '.') or (F.Name = '..')) then
      begin
        if (F.Attr = faDirectory) then
        begin
          if (searchOption = soAllDirectories) then
            Insert(SearchFile(p + PathDelim + F.Name), Result, Length(Result));
        end
        else
        begin
          ts := searchPattern.Replace('.', '\.');
          m := TRegEx.Match(F.Name, ts);
          if m.Success then
            Insert(p + PathDelim + F.Name, Result, Length(Result));
        end;
      end;
      found := FindNext(F);
    end;
  end;

begin
  Result := SearchFile(path);
end;

class function TDirectory.GetFolderPath(nFolder: Integer): string;
var
  pList: PItemIDList;
  cPath: array[0..MAX_PATH] of Char;
begin
  Result := '';
  ZeroMemory(@cPath, sizeof(cPath));
  if SHGetSpecialFolderLocation(0, nFolder, pList) = S_OK then
    if SHGetPathFromIDList(pList, cPath) then
      Result := StrPas(cPath);
end;

class function TDirectory.GetTempPath: string;
var
  arr: array[0..MAX_PATH] of Char;
  num: DWORD;
begin
  num := Winapi.Windows.GetTempPath(MAX_PATH, arr);
  Result := StrPas(arr);
end;

{ TPath }

class procedure TPath.CheckInvalidPathChars(path: string; checkAdditional: Boolean);
begin

  if string.IsNullOrEmpty(path) then
    raise EArgumentException.Create('path');

  if TPath.HasIllegalCharacters(path, checkAdditional) then
    raise EArgumentException.Create('Argument_InvalidPathChars');
end;

class function TPath.HasIllegalCharacters(path: string; checkAdditional: Boolean): Boolean;
var
  I: Integer;
  c: Char;
begin
  Assert(not string.IsNullOrEmpty(path));
  Result := False;
  for I := 0 to path.Length - 1 do
  begin
    c := path[I];

    if (c = '"') or (c = '<') or (c = '>') or (c = '|') or (c < Char(32)) then
    begin
      Result := True;
      Exit;
    end;

    if checkAdditional and ((c = '?') or (c = '*')) then
    begin
      Result := True;
      Exit;
    end;
  end;
end;

{ TFile }

class procedure TFile.WriteAllText(path, contents: string; encoding: TEncoding);
var
  fs: TFileStream;
  bs: TBytes;
begin
  fs := nil;
  try
    fs := TFileStream.Create(path, fmCreate or fmOpenWrite);
    bs := encoding.GetBytes(contents);
    fs.Write(bs, 0, Length(bs));
  finally
    fs.Free;
  end;
end;

class procedure TFile.WriteAllText(path, contents: string);
begin
  TFile.WriteAllText(path, contents, TEncoding.UTF8);
end;

end.

